﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Management;
using System.Threading;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Reflection;
namespace ForceSynch
{
    public partial class Form1 : Form
    {
        private DriveDetector _driveDetector = null;
        private string _config = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)+"\\config.xml";
        public AutoResetEvent trigger = new AutoResetEvent(false);
        private string _driveLetter = string.Empty;
        private string _driveSERIAL = string.Empty;
        AutoResetEvent _usbFinished = new AutoResetEvent(false);
        AutoResetEvent _hddFinished = new AutoResetEvent(false);
        Devices DriveSettings;
        //private event EventHandler<EventArgs> DriveArrivedHandler;
        public Form1()
        {
            DebugTraceListener listener=new DebugTraceListener("ForceSynch");
            Trace.Listeners.Add(listener);
            InitializeComponent();
            if (AutoStarter.IsAutoStartEnabled)
            {
                menuAutostart.CheckState=CheckState.Checked;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
           
            DriveSettings = (Devices)DeserializeObject(ReadFile(_config));
            _driveDetector = new DriveDetector(this);
            _driveDetector.DeviceArrived += new DriveDetectorEventHandler(OnDriveArrived);
            _driveDetector.DeviceRemoved += new DriveDetectorEventHandler(OnDriveRemoved);
            _driveDetector.QueryRemove += new DriveDetectorEventHandler(OnQueryRemove);
            _driveDetector.DriveAdded += new DriveAddedEventHandler(OnDriveAdded);

            string[] DriveList = Environment.GetLogicalDrives();

            for (int i = 0; i < DriveList.Length; i++)
            {
                USBSerialNumber usb = new USBSerialNumber();
                string tmpserial = usb.getSerialNumberFromDriveLetter(DriveList[i].Replace("\\", ""));
                if (tmpserial != null)
                {
                    foreach (Device dev in DriveSettings.DevicesList)
                    {
                        if (dev.DriveID.CompareTo(tmpserial) == 0)
                        {
                            ToolStripMenuItem driveItem = new ToolStripMenuItem(DriveList[i]);
                            driveItem.Tag = new object[] {dev,DriveList[i]};
                            driveItem.Click += new EventHandler(driveItem_Click);
                                //new EventHandler(driveItem_Click(this, new SynchDriveClickEventArgs(dev)));
                            syncronizeToolStripMenuItem.DropDownItems.Add(driveItem);
                        }
                    }
                    
                }

            }
            ThreadPool.QueueUserWorkItem(delegate
            {
                DigestDriveArrived();
            });
        }



        void driveItem_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem s=sender as ToolStripMenuItem;
            Device dev = (Device)((s.Tag as object[])[0]);
            string letter = (string)((s.Tag as object[])[1]);
            //Device dev=(object[])((Device)((ToolStripMenuItem)sender).Tag)[0];
           SyncronizeDirs(dev,letter);
        }

        private string ReadFile(string p)
        {
            StreamReader SR;
            string S;
            SR = File.OpenText(p);
            S = SR.ReadLine();
            while (!SR.EndOfStream)
            {
                Console.WriteLine(S);
                S += SR.ReadLine();
            }
            SR.Close();
            return S;
        }

        public void AddDevice(Device device)
        {

        }

        /// <summary>

        /// To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.

        /// </summary>

        /// <param name="characters">Unicode Byte Array to be converted to String</param>

        /// <returns>String converted from Unicode Byte Array</returns>

        private String UTF8ByteArrayToString(Byte[] characters)
        {

            UTF8Encoding encoding = new UTF8Encoding();

            String constructedString = encoding.GetString(characters);

            return (constructedString);

        }



        /// <summary>

        /// Converts the String to UTF8 Byte array and is used in De serialization

        /// </summary>

        /// <param name="pXmlString"></param>

        /// <returns></returns>

        private Byte[] StringToUTF8ByteArray(String pXmlString)
        {

            UTF8Encoding encoding = new UTF8Encoding();

            Byte[] byteArray = encoding.GetBytes(pXmlString);

            return byteArray;

        }


        /// <summary>
        /// This function receives all the windows messages for this window (form).
        /// We call the DriveDetector from here so that is can pick up the messages about
        /// drives arrived and removed.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (this._driveDetector != null)
            {
                _driveDetector.WndProc(ref m);
            }
        }
        private void OnDriveAdded(Device device)
        {
            List<Device> devices = new List<Device>(DriveSettings.DevicesList);
            devices.Add(device);
            DriveSettings.DevicesList = devices.ToArray();
        }
        private void OnDriveArrived(object sender, DriveDetectorEventArgs e)
        {
            // e.Drive is the drive letter, e.g. "E:\\"

            // If you want to be notified when drive is being removed (and be

            // able to cancel it),

            // set HookQueryRemove to true



            _driveLetter = e.Drive.Replace("\\", "");

            e.HookQueryRemove = true;

            //DriveArrivedHandler.Invoke(this, new DriveArrivedEventArgs(_driveLetter));
            trigger.Set();

        }
        private void DigestDriveArrived()
        {
            if (trigger.WaitOne())
            {
                bool gasit = false;
                USBSerialNumber USB_ = new USBSerialNumber();
                _driveSERIAL = USB_.getSerialNumberFromDriveLetter(_driveLetter);
                foreach (Device dev in DriveSettings.DevicesList)
                {
                    if (dev.DriveID.CompareTo(_driveSERIAL) == 0)
                    {
                        gasit = true;
                        notifyIcon2.BalloonTipTitle = "Drive " + dev.DriveID + " arrived.";
                        notifyIcon2.BalloonTipText = "The "+ _driveLetter + "\\" + dev.DriveDirectory + " is marked as synchronized to " + dev.HddDirectory+"";
                        notifyIcon2.ShowBalloonTip(5000);
                        SyncronizeDirs(dev, _driveLetter);
                    }

                }
                if (!gasit == true)
                {
                    notifyIcon2.BalloonTipTitle = "New drive detected.";
                    notifyIcon2.BalloonTipText = "Un nou drive a sosit.\n Il adaugam la sincronizare?";
                    notifyIcon2.BalloonTipClicked += new EventHandler(notifyIcon2_Click);
                    notifyIcon2.ShowBalloonTip(5000);
                }
                DigestDriveArrived();
            }
        }

        private void SyncronizeDirs(Device usbDevice,string deviceLetter)
        {
            if (Directory.Exists(deviceLetter  + usbDevice.DriveDirectory))
            {
                if (Directory.Exists(usbDevice.HddDirectory))
                {
                    
                }
                else
                {
                   // MessageBox.Show(usbDevice.HddDirectory + " does not exist.");
                    Directory.CreateDirectory(usbDevice.HddDirectory);
                }
                CompareFilesInDirs(deviceLetter + usbDevice.DriveDirectory, usbDevice.HddDirectory);
  
            }
            else
            {
                MessageBox.Show(deviceLetter + usbDevice.DriveDirectory + " does not exist.");
            }
        }
        void CompareFilesInDirs(string dir1,string dir2)
        {
            if (!Directory.Exists(dir2)) Directory.CreateDirectory(dir2);
            Dictionary<string, bool> files = new Dictionary<string, bool>();
            DirectoryInfo di = new DirectoryInfo(dir1);
            FileInfo[] rgFiles = di.GetFiles("*.*");
            foreach (FileInfo fi in rgFiles)
            {
                files[fi.Name] = true;
                SynchronizeFiles(dir1 + "\\" + fi.Name, dir2 + "\\" + fi.Name);
               
                #region comments
                /*

                ThreadPool.QueueUserWorkItem(delegate(object state)
                {
                    int x = (int)state;
                    if (events[x].WaitOne())
                    {
                       Trace.TraceInformation("Fisierul " + x.ToString() + " " + rgFiles[x].FullName.Substring(originalDirectory.Length, rgFiles[x].FullName.Length - originalDirectory.Length));
                        files.Add(rgFiles[x].FullName.Substring(originalDirectory.Length, rgFiles[x].FullName.Length - originalDirectory.Length), hashes[x]);

                    }
                    if ((x+1) == rgFiles.Length)
                    {
                        if (finishEvent!=null) 
                            finishEvent.Set();
                    }
                }, i);

                ThreadPool.QueueUserWorkItem(delegate(object state)
                {
                    int x = (int)state;
                    bool done=false;
                    string hash = string.Empty;
                    while (!done)
                    {
                        try
                        {
                            hash = GetMD5HashFromFile(rgFiles[x].FullName);
                            done = true;
                        }
                        catch (Exception ex)
                        {
                            done = false;
                        }
                    }

                    hashes[x] = hash;
                    events[x].Set();
                }, i);


                i += 1;*/
                #endregion
            }
            DirectoryInfo di2 = new DirectoryInfo(dir2);
            FileInfo[] rgFiles2 = di2.GetFiles("*.*");
            foreach (FileInfo file2 in rgFiles2)
            {
                if (!files.ContainsKey(file2.Name))
                {
                    SynchronizeFiles(dir2 + "\\" + file2.Name,dir1 + "\\" + file2.Name);
                }
            }
            DirectoryInfo[] dirs=di.GetDirectories();
            List<DirectoryInfo> ldirs=dirs.ToList<DirectoryInfo>();
            ldirs.Sort(DirsComparer);
            foreach (DirectoryInfo directory in ldirs )
            {
                //files = files.Concat(GetFilesInDir(directory.FullName, originalDirectory,null)).ToDictionary(e => e.Key, e => e.Value);
                CompareFilesInDirs(directory.FullName, dir2 + "\\"+directory.Name);

            }
            dirs = di2.GetDirectories();
             ldirs = dirs.ToList<DirectoryInfo>();
            ldirs.Sort(DirsComparer);
            foreach (DirectoryInfo directory in ldirs)
            {
                //files = files.Concat(GetFilesInDir(directory.FullName, originalDirectory,null)).ToDictionary(e => e.Key, e => e.Value);
                CompareFilesInDirs(directory.FullName, dir1 + "\\" + directory.Name);
                
            }
          //  return files;
        }
        int DirsComparer(DirectoryInfo d1, DirectoryInfo d2)
        {
            return d1.FullName.CompareTo(d2.FullName);
        }
        private void SynchronizeFiles(string file1, string possibleFile2)
        {
            //The file exists (of course) in the first directory
            //The file does not exist in the second directory
            if (!File.Exists(possibleFile2))
            {
                ThreadPool.QueueUserWorkItem(delegate
                {
                    Helper.CopyFile(file1, possibleFile2);     
                });
            }
            else
            {
                //
                //both files exist
                //
                FileInfo f1 = new FileInfo(file1);
                FileInfo f2 = new FileInfo(possibleFile2);
                int compareResult = Helper.CompareDates(f1.LastWriteTimeUtc, f2.LastWriteTimeUtc);
                if (compareResult==0 ){
                    
                }
                else if (compareResult==-1)
                {
                    Trace.TraceInformation(f1.FullName + " is newer than " + f2.FullName + ". Will overwrite.");
                    Helper.CopyFile(f1.FullName, f2.FullName);
                  
                }
                else //f2 is newer
                {
                    Trace.TraceInformation(f2.FullName + " is newer than " + f1.FullName + ". Will overwrite.");
                    Helper.CopyFile(f2.FullName, f1.FullName);
                   
                }
            }
        }
        protected string GetMD5HashFromFile(string fileName)
        {
            FileStream file = new FileStream(fileName, FileMode.Open);
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] retVal = md5.ComputeHash(file);
            file.Close();

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < retVal.Length; i++)
            {
                sb.Append(retVal[i].ToString("x2"));
            }
            return sb.ToString();
        }
        void notifyIcon2_Click(object sender, EventArgs e)
        {
            AddNewDrive(_driveLetter);
        }

        private void AddNewDrive(string letter)
        {
            FolderBrowserDialog fb = new FolderBrowserDialog();
            fb.Description = "Select HDD directory";
            if (fb.ShowDialog() == DialogResult.OK)
            {
                string hddDirectory = fb.SelectedPath;
                FolderBrowserDialog fb2 = new FolderBrowserDialog();
                fb2.Description = "Select USB directory";
                fb2.SelectedPath = _driveLetter;
                if (fb2.ShowDialog() == DialogResult.OK)
                {
                    DriveSettings.Add(new Device(_driveSERIAL, fb2.SelectedPath, hddDirectory));
                }
            }
        }

        private void OnDriveRemoved(object sender, DriveDetectorEventArgs e)
        {
            // TODO: do clean up here, etc. Letter of the removed drive is in

            // e.Drive;

        }

        // Called by DriveDetector when removable drive is about to be removed

        private void OnQueryRemove(object sender, DriveDetectorEventArgs e)
        {
            // Should we allow the drive to be unplugged?

            if (MessageBox.Show("Allow remove?", "Query remove",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
                    DialogResult.Yes)
                e.Cancel = false;        // Allow removal

            else
                e.Cancel = true;         // Cancel the removal of the device


        }
        /// <summary>

        /// Method to reconstruct an Object from XML string

        /// </summary>

        /// <param name="pXmlizedString"></param>

        /// <returns></returns>

        public Object DeserializeObject(String pXmlizedString)
        {

            XmlSerializer xs = new XmlSerializer(typeof(Devices));

            MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));

            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);

            return xs.Deserialize(memoryStream);

        }
        public string SerializeObject(Object pObject)
        {

            try
            {

                String XmlizedString = null;

                MemoryStream memoryStream = new MemoryStream();

                XmlSerializer xs = new XmlSerializer(typeof(Devices));

                XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);



                xs.Serialize(xmlTextWriter, pObject);

                memoryStream = (MemoryStream)xmlTextWriter.BaseStream;

                XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());

                return XmlizedString;

            }

            catch (Exception e)
            {

                System.Console.WriteLine(e);

                return null;

            }

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {

            TextWriter tw = new StreamWriter(_config);

            // write a line of text to the file
            tw.WriteLine(SerializeObject(DriveSettings));

            // close the stream
            tw.Close();
        }

        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.Close();

        }

        private void toolStripMenuItem2_Click(object sender, EventArgs e)
        {
            System.Windows.Forms.ToolStripMenuItem x = (System.Windows.Forms.ToolStripMenuItem)sender;
            if (x.CheckState == CheckState.Checked)
            {
                x.CheckState = CheckState.Unchecked;
            }
            else
            {
                x.CheckState = CheckState.Checked;
                Installer installer = new Installer();
                //installer.Install();
                AutoStarter.SetAutoStart(installer.Install()+"ForceSynch.exe");
            }
            ;

        }

    }
    public class DriveArrivedEventArgs : EventArgs
    {
        private string _driveLetter;
        public DriveArrivedEventArgs(string x)
        {
            _driveLetter = x;
        }
        public string DriveLetter
        {
            get { return _driveLetter; }
        }
    }
    class SynchDriveClickEventArgs : EventArgs
    {
        private Device _device;
        public Device Device
        {
            get { return _device; }
        }
        public SynchDriveClickEventArgs(Device device)
        {
            this._device = device;
        }
    }
}

    

